package block

import (
	"WeiXinCode/区块链学习/01_day_hdv2/utils"
	"bytes"
	"crypto/ecdsa"
	"fmt"
	"github.com/btcsuite/btcutil/base58"
	"log"
	"os"

	"github.com/boltdb/bolt"
)

//创建区块链对象 老版本
//type BlockChain struct {
//	Blocks []*Block
//}

//创建区块链对象 新版本

type BlockChain struct {
	Db   *bolt.DB // bolt数据库的句柄 数据库操作对象
	Tali []byte   //最后一个区块的哈希值
}

var tail []byte                   //时刻存取最后一个区块的哈希值
const lastHashKey = "lastHashKey" // 最后一个区块的哈希值的key
const genesisByte = "新世界就见到来，我将代表新人类！"
const blockBucket = "blockBucket"              //数据库桶文件名
const blockChainName = "blockChain.db"         //数据库名称
const MainBlockName uint8 = 0x0000000000000000 // 创世区块的哈希值
const reward float64 = 12.5                    //12.5为挖矿奖励  reward挖矿奖励

//实现创建区块链方法
func CreateBlockChain(miner string) *BlockChain {
	//判断数据库是否存在
	if utils.IsFileExist(blockChainName) {
		fmt.Println("区块链已经存在，不需要重复创建！")
		return nil
	}

	//功能分析
	// 1.获取数据库句柄 ，打开数据库，读取数据库
	// 创建一个数据库 以及权限
	db, err := bolt.Open(blockChainName, 0600, nil)
	if err != nil {
		log.Panic(err)
	}
	// 打开数据库
	db.Update(func(tx *bolt.Tx) error {
		fmt.Println("申请创建区块中，即将创建")
		b, err := tx.CreateBucket([]byte(blockBucket))
		if b == nil {
			fmt.Println("创建失败！，请删除当前目录.db文件")
			os.Exit(1)
		}
		// 为数据库添加一个创世块
		// v5创世块只有一个挖矿交易coinbash // genesisByte可以任意填写
		coinbash := NewCoinbaseTx(miner, genesisByte)
		//genesisBlock := NewBlock([]byte(genesisByte), []byte{MainBlockName}) 老版
		genesisBlock := NewBlock([]*Transaction{coinbash}, []byte{MainBlockName})
		/* 吧区块内容序列化，转成字节流存入数据库中*/
		err = b.Put(genesisBlock.Hash, genesisBlock.Serialization())
		if err != nil {
			log.Panic(err)
		}
		// 2.获取最后一个区块的哈希值
		b.Put([]byte(lastHashKey), genesisBlock.Hash)
		// 保存第一个hash值，保证他传出去
		tail = genesisBlock.Hash
		// 测试读取
		//blockinfo := b.Get(genesisBlock.Hash)
		//block := Deserialization(blockinfo)
		//fmt.Println("解码后数据为：", block)

		return nil
	})

	//【2】直接获取最后一个区块的哈希值

	return &BlockChain{
		Db:   db,
		Tali: tail,
	}
}

//返回区块链实例
func NewBlockChain() *BlockChain {
	//判断数据库是否存在
	if !utils.IsFileExist(blockChainName) {
		fmt.Println("区块链不存在，请先创建！")
		return nil
	}
	//功能分析
	// 1.获取数据库句柄 ，打开数据库，读取数据库
	// 创建一个数据库 以及权限
	db, err := bolt.Open(blockChainName, 0600, nil)
	if err != nil {
		log.Panic(err)
	}
	// 打开数据库
	db.View(func(tx *bolt.Tx) error {
		// 判断是否有buket，如果有直接【2】，没有就创建
		b := tx.Bucket([]byte(blockBucket))
		if b == nil {
			fmt.Println("区块链数据库blockBucket桶不存在，请检查！")
			os.Exit(1)
		}
		//如果存在数据库，我就查找出来，返回
		tail = b.Get([]byte(lastHashKey))
		fmt.Println("存在blockBucket")

		return nil
	})

	//【2】直接获取最后一个区块的哈希值

	return &BlockChain{
		Db:   db,
		Tali: tail,
	}
}

//实现区块添加的方法
func (bc *BlockChain) AddBlock(txs []*Transaction) {
	//矿工得到交易时，第一时间对交易进行验证
	//矿工如果不验证，即使挖矿成功，广播区块后，其他矿工仍然会校验每一笔交易
	validTXs:=[]*Transaction{}

	for _,tx:=range txs {
		if bc.VerifyTransaction(tx) {
			fmt.Println("交易有效！：",tx.TXid)
			validTXs=append(validTXs, tx)
		}else {
			fmt.Println("发现这笔交易无效：",tx.TXid)
		}
	}
	// 打开数据库
	bc.Db.Update(func(tx *bolt.Tx) error {
		// 判断是否有buket，如果有直接【2】，没有就创建
		b := tx.Bucket([]byte(blockBucket))
		if b == nil {
			fmt.Println("blockBucket不存在，请创建创世块！")
			os.Exit(1) //推出程序
		} else {
			// 新建一个区块
			// 读取数据库最后一个哈希值 【我的】
			//tail =b.Get([]byte(lastHashKey))
			//block := NewBlock(data, tail)

			// 读取数据库最后一个哈希值 【老师】
			block := NewBlock(validTXs, bc.Tali)
			err := b.Put(block.Hash, block.Serialization())
			if err != nil {
				log.Panic(err)
			}
			// 2.写入最后一个哈希值在数据库中
			err = b.Put([]byte(lastHashKey), block.Hash)
			if err != nil {
				log.Panic(err)
			}
			//更新新的哈希值
			bc.Tali = block.Hash
			fmt.Println("存入数据库成功！")
		}

		return nil
	})
}

/**
定义一个区块链数据库迭代器 结构体
	1.先根据数据库最后一个哈希值读取最后一个区块
	2.然后根据最后一个区块里面block里的上一个区块哈希值读取上一个区块
	3.依次读取完成所有的区块内容。
*/
type BlockChainInterator struct {
	Db      *bolt.DB // 用于查询数据库的操作类
	current []byte   // 时刻保存下一个要查询数据库的key（哈希值）
}

// 创建迭代器
func (bc *BlockChain) NewInterator() *BlockChainInterator {
	return &BlockChainInterator{
		Db:      bc.Db,
		current: bc.Tali,
	}
}

func (it *BlockChainInterator) Next() *Block {
	var block Block
	it.Db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blockBucket))
		if b == nil {
			fmt.Println("blockBucket不存在，请创建创世块！")
			os.Exit(1) //推出程序
		}
		//数据库读取数据
		blockInfo := b.Get(it.current) // blockInfo字节流
		//blockInfo字节流反序列化
		block = *Deserialization(blockInfo)
		it.current = block.PrevBlockHash //获取上一个区块的哈希值

		return nil
	})
	return &block
}

//v6 整合 FindMyUtoxs/FindNeedUtoxs 同时包含所有已知信息
type UTXOInfo struct {
	TXID   []byte   //当前区块的交易id
	Index  int64    //output的索引值
	Output TXOutput // output本身
}

//获取用户余额实现思路
//
//1.遍历账本
//2.遍历交易
//3.遍历output
//4.找到属于我的所有output
//找到区块里面，所有属于我的余额信息，并且没有被消耗的TXOutput[]
func (bc *BlockChain) FindMyUtoxs(pubKeyHash []byte) []UTXOInfo {
	it := bc.NewInterator()
	spentUTXOs := make(map[string][]int64)
	//var UTXOs []TXOutput //优先定义结构体
	var UTXOInfos []UTXOInfo // 新的返回结果
	// 0循环遍历账本
	for {
		block := it.Next() //获取上一块区块

		// 1遍历区块体Outputs
		for _, tx := range block.Transactions {
			// 1遍历区块体intputs
			if tx.ISCoinbase() == false {
				//判断是否是挖矿交易，是的话就跳过遍历
				for _, input := range tx.TXInputs {
					//判断当前被使用的input是否为目标地址pubKeyHash所有
					if bytes.Equal(HashPubHash(input.Pubkey), pubKeyHash) {
						fmt.Println("找到了消耗过的output！,index:", input.Index)
						key := string(input.TXID)
						spentUTXOs[key] = append(spentUTXOs[key], input.Index)
					}
				}
			}

		OUTPUT:
			//2遍历output
			for i, output := range tx.TXOutputs {

				key := string(tx.TXid)
				indexes := spentUTXOs[key]
				if len(indexes) != 0 {
					fmt.Println("这笔交易有存在被消耗过的output")
					for _, j := range indexes {
						if int64(i) == j {
							fmt.Println("i=j,当前output已经被消耗，不参与累加数据")
							continue OUTPUT //跳到下一次循环
						}
					}
				}

				//3找到属于我的output
				if bytes.Equal(pubKeyHash, output.PubKeyHash) {
					//UTXOs = append(UTXOs, output)
					u := UTXOInfo{
						TXID:   tx.TXid,
						Index:  int64(i),
						Output: output,
					}
					UTXOInfos = append(UTXOInfos, u)
					fmt.Println("找到了属于：", pubKeyHash, "的output，", i)
				}
			}
		}
		//4.遍历到最后一个区块的时候结束
		if bytes.Equal(block.PrevBlockHash, []byte{MainBlockName}) {
			fmt.Println("区块链遍历结束")
			break
		}
	}

	return UTXOInfos
}

//计算属于我的余额信息
func (bc *BlockChain) GetBalance(address string) {

	// 把转账地址转为25字节数据
	publicInfo := base58.Decode(address)
	// 取1到20个字节数据
	pubKeyHash := publicInfo[1 : len(publicInfo)-4]

	UTXOInfos := bc.FindMyUtoxs(pubKeyHash)

	// 先累加所有 老版
	var total = 0.0
	//for _, utxo := range utxos {
	//	total += utxo.Value
	//}

	//v6 版本
	for _, utxoinfo := range UTXOInfos {
		total += utxoinfo.Output.Value
	}

	fmt.Println("用户名：", address, ",的余额为：", total, " 。")
}

/**
// 参数：付款人base58公钥hash，转账金额 1. 遍历账本，找到属于付款人的合适的金额，把这个outputs找到。
	utxos, resValue = bc.FindNeedUtoxs(from, amout)
	找到所有属于我的并且没有消耗过的make(map[string][]int64)// 当前区块链交易id号，与id号所对应的金额，与float64遍历到的足以交易的金钱数量
*/
func (bc *BlockChain) FindNeedUtoxs(pubKeyHash []byte, amout float64) (map[string][]int64, float64) {
	needutxos := make(map[string][]int64)
	// 用于记录累加所有属于用户名的output金额
	var resValue = 0.0
	//+++++++++++++++++++++++++++++++++++++++++++++++++
	//稍后实现
	fmt.Println("获取属于：", pubKeyHash, ",所有TXOutput。")
	//var UTXOs []TXOutput //优先定义结构体
	//v6实现 FindMyUtoxs已经遍历并且找到了所有有关from用户的outputs信息了
	UTXOInfos := bc.FindMyUtoxs(pubKeyHash)
	//在这里就要判断 获取的金额总和是否足够amout这个转账金额消耗 v6版本
	for _, utxoinfo := range UTXOInfos {
		key := utxoinfo.TXID
		needutxos[string(key)] = append(needutxos[string(key)], utxoinfo.Index)
		resValue += utxoinfo.Output.Value

		//2.判断一下金额是否足够 //判断金额是否足够 足够返回，不足继续遍历
		if resValue >= amout {
			break
			//足够了我就跳出循环，函数本身不计算，跳出后得到的后在计算是否能用不能用
		}
	}
	return needutxos, resValue
}

func (bc *BlockChain) SignTransaction(tx *Transaction, privateKey *ecdsa.PrivateKey) {
	//遍历账本找到所有应用交易
	prevTXs := make(map[string]Transaction)
	fmt.Println("遍历账本中...")
	for _, input := range tx.TXInputs {
		prevTx := bc.FindTransaction(input.TXID)

		if prevTx == nil {
			fmt.Println("没有找到此交易：", input.TXID)
		} else {
			prevTXs[string(input.TXID)] = *prevTx
		}
	}
	tx.Sign(privateKey, prevTXs)
}

// 矿工校验流程
// 1.找到交易input所引用的所有交易prevTXs
//2.对交易进行校验
func (bc *BlockChain)VerifyTransaction(tx *Transaction) bool {
	if tx.ISCoinbase() {
		return true
	}
	//遍历账本找到所有应用交易
	prevTXs := make(map[string]Transaction)
	fmt.Println("遍历账本中...")
	for _, input := range tx.TXInputs {
		prevTx := bc.FindTransaction(input.TXID)

		if prevTx == nil {
			fmt.Println("没有找到此交易：", input.TXID)
		} else {
			prevTXs[string(input.TXID)] = *prevTx
		}
	}
	return tx.Verify(prevTXs)
}
func (bc *BlockChain) FindTransaction(txid []byte) *Transaction {
	//遍历区块链交易
	//通过对比id来识别
	it := bc.NewInterator()

	for {
		block := it.Next()

		for _,tx:=range block.Transactions{
			if bytes.Equal(tx.TXid,txid) {
				return tx
			}
		}

		if len(block.PrevBlockHash) ==0{
			break
		}
	}
	return nil
}
